﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Isuka.Linq
{

    /// <summary>
    /// Linq 拡張
    /// </summary>
    public static partial class EnumerableExtensions
    {

        /// <summary>
        /// 1回目と2回目以降で異なる関数を実行するAggregate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TA"></typeparam>
        /// <param name="source"></param>
        /// <param name="seed"></param>
        /// <param name="head"></param>
        /// <param name="tail"></param>
        /// <returns></returns>
        public static TA Aggregate<T, TA>(this IEnumerable<T> source, TA seed, Func<TA, T, TA> head, Func<TA, T, TA> tail)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(head != null, "head");
            Contract.Requires<ArgumentNullException>(tail != null, "tail");

            var a = seed;
            using (var e = source.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    a = head(a, e.Current);
                    while (e.MoveNext())
                    {
                        a = tail(a, e.Current);
                    }
                }
                return a;
            }
        }

        /// <summary>
        /// 特定の値の組を無限にループさせる
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="values"></param>
        /// <returns></returns>
        public static IEnumerable<T> Cycle<T>(params T[] values)
        {
            Contract.Requires<ArgumentNullException>(values != null, "values");
            Contract.Requires<ArgumentException>(0 < values.Length);

            return CycleIterator(values);
        }

        static IEnumerable<T> CycleIterator<T>(T[] values)
        {
            while (true)
            {
                foreach (var item in values)
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// 関数オブジェクトを引数とする DefaultIfEmpty
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static IEnumerable<T> DefaultIfEmpty<T>(this IEnumerable<T> source, Func<T> defaultValue)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(defaultValue != null, "action");

            return source.Any() ? source : Enumerable.Repeat(defaultValue(), 1);
        }

        /// <summary>
        /// ループ処理。Ix 利用すべし
        /// 何気に Do っぽくも使える
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="action"></param>
        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(action != null, "action");
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

            foreach (var item in source)
            {
                action(item);
            }
            return source;
        }

        public static T[] ForEach<T>(this T[] source, Action<T> action)
        {
            Contract.Requires(source != null);
            Contract.Requires(action != null);
            Contract.Ensures(Contract.Result<T[]>() != null);

            Array.ForEach(source, action);
            return source;
        }

        /// <summary>
        /// ループ処理。Ix 利用すべし
        /// 何気に Do っぽくも使える
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="action"></param>
        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(action != null, "action");
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

            int i = 0;
            foreach (var item in source)
            {
                action(item, i);
                i++;
            }
            return source;
        }

        public static T[] ForEach<T>(this T[] source, Action<T, int> action)
        {
            Contract.Requires(source != null);
            Contract.Requires(action != null);
            Contract.Ensures(Contract.Result<T[]>() != null);

            for (var i = 0; i < source.Length; i++)
            {
                action(source[i], i);
            }
            return source;
        }

        /// <summary>
        /// シーケンスが空かどうか
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsEmpty(this IEnumerable source)
        {
            Contract.Requires(source != null);

            foreach (var item in source)
            {
                return true;
            }
            return false;
        }
        public static bool IsEmpty<T>(this IEnumerable<T> source)
        {
            Contract.Requires(source != null);
            return !source.Any();
        }

        /// <summary>
        /// シーケンス内の項目が1つだけである場合 true
        /// </summary>
        /// <param name="source">元ソース</param>
        /// <returns>シーケンス内の項目が1つだけであれば true</returns>
        public static bool IsSingle(this IEnumerable source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");

            var flag = false;
            foreach (var item in source)
            {
                if (flag)
                    return false;
                flag = true;
            }
            return flag;
        }
        public static bool IsSingle<T>(this IEnumerable<T> source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");

            using (var e = source.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    return !e.MoveNext();
                }
                else
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// 要素数が n の倍数になるように pad を追加する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="pad"></param>
        /// <returns></returns>
        public static IEnumerable<T> Padding<T>(this IEnumerable<T> source, int n, T pad = default(T))
        {
            Contract.Requires<ArgumentException>(n > 0);
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

            return PaddingIterator(source, n, pad);
        }

        public static IEnumerable<T> Padding<T>(this T[] source, int n, T pad = default(T))
        {
            Contract.Requires<ArgumentException>(n > 0);
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

            return PaddingIterator(source, n, pad);
        }

        static IEnumerable<T> PaddingIterator<T, TEnumerable>(TEnumerable source, int n, T pad = default(T))
            where TEnumerable : IEnumerable<T>
        {
            long c = 0;
            foreach (var item in source)
            {
                checked
                {
                    c++;
                }
                yield return item;
            }
            var p = c % n;
            for (var i = 0L; i < p; i++)
            {
                yield return pad;
            }
        }

        /// <summary>
        /// 射影しつつ、条件に一致する最初の項目を IEnumerable でラップして取得する
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static IEnumerable<TResult> SelectFirstAsEnumerable<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, Predicate<TResult> predicate)
        {
            Contract.Requires<ArgumentNullException>(selector != null, "selector");
            Contract.Requires<ArgumentNullException>(predicate != null, "predicate");

            if (source != null)
            {
                foreach (var item in source)
                {
                    var result = selector(item);
                    if (predicate(result))
                    {
                        return new TResult[] { result };
                    }
                }
            }
            return Enumerable.Empty<TResult>();
        }

        /// <summary>
        /// 射影しつつ、条件に一致する最初の項目を取得する
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static TResult SelectFirstOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
        {
            Contract.Requires<ArgumentNullException>(selector != null, "selector");

            return SelectFirstOrDefault(source, selector, x => true);
        }

        /// <summary>
        /// 射影しつつ、条件に一致する最初の項目を取得する
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static TResult SelectFirstOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, Predicate<TResult> predicate)
        {
            Contract.Requires<ArgumentNullException>(selector != null, "selector");
            Contract.Requires<ArgumentNullException>(predicate != null, "predicate");

            if (source != null)
            {
                foreach (var item in source)
                {
                    var result = selector(item);
                    if (predicate(result))
                        return result;
                }
            }
            return default(TResult);
        }

        public static IEnumerable<T> Single<T>(T value)
        {
            yield return value;
        }

        public static string Stringize(this IEnumerable<char> values)
        {
            Contract.Requires(values != null);
            Contract.Ensures(Contract.Result<string>() != null);

            return new string(values.ToArray());
        }

        /// <summary>
        /// 文字列シーケンスを連結する
        /// </summary>
        /// <param name="values"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public static string Stringize(this IEnumerable<string> values, string separator)
        {
            Contract.Requires(values != null);
            Contract.Ensures(Contract.Result<string>() != null);

            var result = string.Join(separator, values);
            Contract.Assume(result != null);
            return result;
        }

        /// <summary>
        /// 任意のシーケンスを文字列に変換して連結する。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="values"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public static string Stringize<T>(this IEnumerable<T> values, string separator)
        {
            Contract.Requires(values != null);
            Contract.Ensures(Contract.Result<string>() != null);

            var result = string.Join(separator, values);
            Contract.Assume(result != null);
            return result;
        }

        /// <summary>
        /// キーと値のペアで構成されるシーケンスを、辞書に変換する
        /// </summary>
        /// <typeparam name="TKey">キーの型</typeparam>
        /// <typeparam name="TValue">値の型</typeparam>
        /// <param name="source">キーと値のペアで構成されるシーケンス</param>
        /// <returns>辞書</returns>
        public static IDictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source)
        {
            Contract.Requires(source != null);
            Contract.Ensures(Contract.Result<IDictionary<TKey, TValue>>() != null);

            return source.ToDictionary(x => x.Key, x => x.Value);
        }

        public static void WhenEmpty(this IEnumerable source, Action action)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(action != null, "action");

            if (source.IsEmpty())
            {
                action();
            }
        }
        public static void WhenEmpty<T>(this IEnumerable<T> source, Action action)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(action != null, "action");

            if (!source.Any())
            {
                action();
            }
        }

    }
}
